#include <iostream>
#include <sstream>
#include <ruby.h>
#include "m3g/m3g.hpp"
#include "ruby-m3g.hpp"
using namespace m3g;
using namespace std;

namespace {
    struct ValuesAccessor {
        VertexArray* vertex_array;
    };
    VALUE rb_cVertexArray_ValuesAccessor;
}

void ruby_VertexArray_mark (VertexArray* ptr)
{
    vector<Object3D*> objs;
    extract_all_references (ptr, objs);
    for (int i = 0; i < (int)objs.size(); i++) {
        VALUE val_obj = (VALUE)objs[i]->getExportedEntity();
        if (val_obj)
            rb_gc_mark (val_obj);
    }
}

void ruby_VertexArray_free (VertexArray* ptr)
{
    if (valid_ptr((unsigned char*)ptr, sizeof(*ptr))) {
        __TRY__;
        ptr->~VertexArray ();
        __CATCH__;
    }
    ruby_xfree (ptr);
}

VALUE ruby_VertexArray_allocate (VALUE self)
{
    void* p = ruby_xmalloc (sizeof(VertexArray));
    return Data_Wrap_Struct (self, ruby_VertexArray_mark, ruby_VertexArray_free, p);
}

VALUE ruby_VertexArray_initialize (VALUE self, VALUE val_num_vertices, VALUE val_num_components, VALUE val_component_size)
{
    VertexArray* p;
    Data_Get_Struct (self, VertexArray, p);
    int num_vertices    = NUM2INT (val_num_vertices);
    int num_components  = NUM2INT (val_num_components);
    int component_size  = NUM2INT (val_component_size);
    __TRY__;
    new (p) VertexArray (num_vertices, num_components, component_size);
    __CATCH__;
    p->setExportedEntity ((void*)self);
    return self;
}

VALUE ruby_VertexArray_get (VALUE self)
{
    VertexArray* p;
    Data_Get_Struct (self, VertexArray, p);
    ValuesAccessor* accessor;
    VALUE       val_accessor = Data_Make_Struct (rb_cVertexArray_ValuesAccessor, ValuesAccessor, 0, -1, accessor);
    accessor->vertex_array = p;
    return val_accessor;
}

VALUE ruby_VertexArray_get_component_count (VALUE self)
{
    VertexArray* p;
    Data_Get_Struct (self, VertexArray, p);
    int component_count;
    __TRY__;
    component_count = p->getComponentCount ();
    __CATCH__;
    return INT2NUM(component_count);
}

VALUE ruby_VertexArray_get_component_type (VALUE self)
{
    VertexArray* p;
    Data_Get_Struct (self, VertexArray, p);
    int component_type;
    __TRY__;
    component_type = p->getComponentType ();
    __CATCH__;
    return INT2NUM(component_type);
}

VALUE ruby_VertexArray_get_vertex_count (VALUE self)
{
    VertexArray* p;
    Data_Get_Struct (self, VertexArray, p);
    int vertex_count;
    __TRY__;
    vertex_count = p->getVertexCount ();
    __CATCH__;
    return INT2NUM(vertex_count);
}


VALUE ruby_VertexArray_convert (VALUE self, VALUE val_component_type)
{
    VertexArray* p;
    Data_Get_Struct (self, VertexArray, p);
    int component_type = NUM2INT(val_component_type);
    __TRY__;
    p->convert (component_type);
    __CATCH__;
    return Qnil;
}

VALUE ruby_VertexArray_print (VALUE self)
{
    VertexArray* p;
    Data_Get_Struct (self, VertexArray, p);
    ostringstream out;
    __TRY__;
    p->print (out);
    __CATCH__;
    return rb_str_new2 (out.str().c_str());
}

/**
 * VertexArray:: ValuesAccessor
 */
VALUE ruby_VertexArray_ValuesAccessor_get (VALUE self, VALUE val_first_vertex, VALUE val_num_vertices)
{
    ValuesAccessor* p;
    Data_Get_Struct (self, ValuesAccessor, p);

    int component_size  = p->vertex_array->getComponentType ();
    int component_count = p->vertex_array->getComponentCount ();
    int first_vertex    = NUM2INT(val_first_vertex);
    int num_vertices    = NUM2INT(val_num_vertices);

    union {
        char*  values_char;
        short* values_short;
        float* values_float;
    };
    values_char = (char*)ruby_xmalloc(component_size*component_count*num_vertices);

    switch (component_size) {
    case 1: p->vertex_array->get (first_vertex, num_vertices, values_char ); break;
    case 2: p->vertex_array->get (first_vertex, num_vertices, values_short); break;
    case 4: p->vertex_array->get (first_vertex, num_vertices, values_float); break;
    }
    VALUE val_values = rb_ary_new2 (component_count * num_vertices);
    for (int i = 0; i < num_vertices * component_count; i++) {
        switch (component_size) {
        case 1: rb_ary_push (val_values, INT2NUM     (values_char [i])); break;
        case 2: rb_ary_push (val_values, INT2NUM     (values_short[i])); break;
        case 4: rb_ary_push (val_values, rb_float_new(values_float[i])); break;
        }
    }
    ruby_xfree (values_char);

    return val_values;
}

VALUE ruby_VertexArray_ValuesAccessor_set (VALUE self, VALUE val_first_vertex, VALUE val_num_vertices, VALUE val_values)
{
    rb_check_type (val_values, T_ARRAY);

    ValuesAccessor* p;
    Data_Get_Struct (self, ValuesAccessor, p);
    int first_vertex    = NUM2INT(val_first_vertex);
    int num_vertices    = NUM2INT(val_num_vertices);
    int component_size  = p->vertex_array->getComponentType ();
    int component_count = p->vertex_array->getComponentCount ();
    if (RARRAY_LEN(val_values) < num_vertices * component_count) {
        rb_raise (rb_eIllegalArgumentException, "values.length must be equal or greater than num_vertices*component_count");
    }
    union {
        char*  values_char;
        short* values_short;
        float* values_float;
    };
    values_char = (char*)ruby_xmalloc(component_size*component_count*num_vertices);

    for (int i = 0; i < num_vertices*component_count; i++) {
        switch (component_size) {
        case 1: values_char [i] = NUM2INT(rb_ary_entry(val_values, i)); break;
        case 2: values_short[i] = NUM2INT(rb_ary_entry(val_values, i)); break;
        case 4: values_float[i] = NUM2DBL(rb_ary_entry(val_values, i)); break;
        }
    }
    switch (component_size) {
    case 1: p->vertex_array->set (first_vertex, num_vertices, values_char ); break;
    case 2: p->vertex_array->set (first_vertex, num_vertices, values_short); break;
    case 4: p->vertex_array->set (first_vertex, num_vertices, values_float); break;
    }
    ruby_xfree (values_char);

    return Qnil;
}

/**
 *
 */
void register_VertexArray ()
{
    // VertexArray
    rb_cVertexArray         = rb_define_class_under (rb_mM3G, "VertexArray",         rb_cObject3D);

    rb_define_alloc_func (rb_cVertexArray, ruby_VertexArray_allocate);
    rb_define_private_method (rb_cVertexArray, "initialize", (VALUE(*)(...))ruby_VertexArray_initialize, 3);

    rb_define_method (rb_cVertexArray, "component_count", (VALUE(*)(...))ruby_VertexArray_get_component_count, 0);
    rb_define_method (rb_cVertexArray, "component_type",  (VALUE(*)(...))ruby_VertexArray_get_component_type,  0);
    rb_define_method (rb_cVertexArray, "vertex_count",    (VALUE(*)(...))ruby_VertexArray_get_vertex_count,    0);
    rb_define_method (rb_cVertexArray, "values"      ,    (VALUE(*)(...))ruby_VertexArray_get             ,    0);
    rb_define_method (rb_cVertexArray, "convert",         (VALUE(*)(...))ruby_VertexArray_convert,             1);
    rb_define_method (rb_cVertexArray, "to_s",              (VALUE(*)(...))ruby_VertexArray_print,                0);

    // VertexArray:: ValuesAccessor
    rb_cVertexArray_ValuesAccessor = rb_define_class_under (rb_cVertexArray, "ValuesAccessor", rb_cObject);

    rb_define_method (rb_cVertexArray_ValuesAccessor, "[]" ,   (VALUE(*)(...))ruby_VertexArray_ValuesAccessor_get,   2);
    rb_define_method (rb_cVertexArray_ValuesAccessor, "[]=",   (VALUE(*)(...))ruby_VertexArray_ValuesAccessor_set,   3);

}
